

// epoll 的接口
/*
#include <sys/epoll.h>



11111. 创建一个epoll模型
int epoll_create(int size); 
                返回一个文件描述符, size 在内核 2.6.8之后被忽略, 传入一个大于0 的数就行了 



22222.用户告诉内核要帮我关心那些文件描述符和那些事件
int epoll_ctl(int epfd,  // epoll_create返回的epoll 句柄
              int op,    //操作, 新增/修改/删除 : EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL
              int fd,    //fd不解释
              struct epoll_event *event  // fd关心的事件
              );

typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;       // 对应的关心的事件 例如: EPOLLIN/EPOLLOUT/EPOLLPRI
    epoll_data_t data;         // 事件到来的时候 ,知道 那个 fd 就绪了 
};



33333.内核告诉用户什么事件就绪了, 
int epoll_wait(int epfd, // epoll_create返回的epoll 句柄
                struct epoll_event *events,  //输出型参数, 是一个数组, 读取底层的就绪队列, 
                int maxevents,  // 读取的就绪队列中就绪fd 的最大值,, 如果一次读不完 也没有关系, 下次再次读取 
                int timeout // 和 poll 的timeout相同
                );

返回值: 和 epoll/select相同







*/




// epoll 的底层
/*

为了处理大量的句柄 , 而做出了epoll_

1.OS在创建epoll的时候 ,会给我们创建一颗红黑树 : 用户告诉内核, 那些sock上的那些event事件 OS 要关心

2.OS还创建了一个就绪队列,: 内核告诉用户, 那些sock上的event事件就绪了

3.每一个节点对应一个文件fd, 底层有struct file {} 对象 对象里面包含了 一个回调函数(这个回调函数的作用就是把红黑树上的节点链接到就绪队列里面) , 

4.当底层有事件就绪了 , OS会调用这个回调函数, 直接执行 

5.上面的所有 就叫 epoll模型, 

6.epoll_create 就是创建上面的epoll模型, epoll_ctl :操作红黑树, epoll_wait : 检测就绪队列就可以了

7.epoll 高效的原因就是, 它不需要去遍历检测, 只需要便利拷贝就绪队列

8.epoll 把主题工作都交给了操作系统

9.epoll 相比与 select 和 poll 不需要自己维护辅助数组,  直接OS 系统在底层帮你维护了一个红黑树


*/


/*

什么是事件就绪, 对于读来说 就是,底层的数据来了, 对应写来说就是底层有空间可以写了 
其实就是底层的 IO 条件满足了 , 可以进行某种IO操作了, 就叫事件就绪了 

select/poll/epoll -> 等待 ->  IO就绪事件的通知机制: epoll 工作模式


epoll 有两个工作模式 LT(level triggered)水平触发   ET(edge triggered)边缘触发
LT :只要底层有数据没读完, epoll就会一直通知用户,要求用户读取数据 (select/poll/epoll 默认都是 LT 模式 )
ET :只要底层有数据没读完, e'poll不在通知用户,除非底层的数据变化的时候(增多了), 才会再通知你一次

LT :就是只要没读完就接着通知你
ET :就是只通知你一次, 没读完后果自负

ET :底层只有在数据从无到有, 从有到多的变化的时候, 才会通知上层, 只会通知一次, -> 倒逼程序员将本轮的就绪的数据全部读取到上层

ET的时候你怎么知道你把本次底层的数据读取完毕了?????
循环读取,直到读不出数据, -> 一般的fd, 是阻塞式的fd, 在 ET下的fd必须是非阻塞的
LT 阻塞/非阻塞 都可以 LT模式下, 我也可以模仿ET的工作方式,一次通知全部读取, 
LT和ET只是通知机制不同, 应用场景不一样

为什么ET比LT更高效????
1.ET只通知一次
2.ET会倒逼程序员一次通知把数据全部读完
3.上层尽快把数据取走,底层的接受缓冲区更大 -> 可以给对方发送一个更大的窗口, 让对方更新出更大的滑动窗口 -> 提高了底层的数据发送效率


tcp协议的psh标志的作用, 让底层数据就绪,再让上层知道 -> 让上层赶紧读取数据







*/